package com.googlecode.connectlet.machine.provider;

import java.io.IOException;
import java.net.InetAddress;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateCrtKey;

import com.googlecode.connectlet.crypto.rsa.RSA;
import com.googlecode.connectlet.crypto.rsa.provider.CompactKeySpec;
import com.googlecode.connectlet.crypto.rsa.provider.RSAPublicKeyImpl;
import com.googlecode.connectlet.machine.MachinePacket;
import com.googlecode.connectlet.machine.provider.key.MachinePrivateKey;

public class MachineKey extends MachineConnection {
	private int keyId;

	protected MachineKey(MachineKey key) {
		this(key, key.keyId);
	}

	public MachineKey(MachineConnection conn, int keyId) {
		super(conn);
		this.keyId = keyId;
	}

	public MachineKey(InetAddress ip, final int port, int keyId) {
		super(ip, port);
		this.keyId = keyId;
	}

	public MachineKey(String host, int port, int keyId) throws IOException {
		super(host, port);
		this.keyId = keyId;
	}

	public int getKeyId() {
		return keyId;
	}

	public void destory() {
		try {
			process(new MachinePacket(MachinePacket.TAG_KEY_DEL, keyId));
		} catch (Exception e) {
			throw new RuntimeException("Connection refused: " + this, e);
		}
	}

	public byte[] wrapKey(byte[] key) {
		try {
			return process(new MachinePacket(MachinePacket.TAG_KEY_WRAP, keyId, 0, key)).getKey();
		} catch (Exception e) {
			throw new RuntimeException("Connection refused: " + this, e);
		}
	}

	public MachinePrivateKey generatePrivate(RSAPrivateCrtKey key) {
		byte[] encodedKey = wrapKey(new CompactKeySpec(key).getEncoded());
		return new MachinePrivateKey(encodedKey, key.getPrimeP().bitLength() * 2);
	}

	public KeyPair generateKeyPair(int keySize) {
		try {
			MachinePacket request = new MachinePacket(MachinePacket.TAG_KEYPAIR_NEW, keyId, keySize);
			MachinePacket response = process(request);
			RSAPublicKeyImpl publicKey = new CompactKeySpec(response.getKey()).getPublicKey();
			MachinePrivateKey privateKey = new MachinePrivateKey(response.getData(), keySize);
			return new KeyPair(publicKey, privateKey);
		} catch (Exception e) {
			throw new RuntimeException("Connection refused: " + this, e);
		}
	}

	public byte[] rsadp(MachinePrivateKey key, byte[] data) {
		try {
			MachinePacket request = new MachinePacket(MachinePacket.
					TAG_PRIVATE_RSADP, keyId, 0, key.getKey(), data);
			return process(request).getData();
		} catch (Exception e) {
			throw new RuntimeException("Connection refused: " + this, e);
		}
	}

	public byte[] decrypt(MachinePrivateKey key, byte[] data) {
		return RSA.unpadDecrypt(rsadp(key, data));
	}

	public byte[] sign(MachinePrivateKey key, byte[] data) {
		return rsadp(key, RSA.padSign(data, (key.getKeySize() + 7) / 8));
	}

	public byte[] encryptCbc(byte[] key, byte[] data) {
		try {
			MachinePacket request = new MachinePacket(MachinePacket.
					TAG_SECRET_ENC_CBC, keyId, 0, key, data);
			return process(request).getData();
		} catch (Exception e) {
			throw new RuntimeException("Connection refused: " + this, e);
		}
	}

	public byte[] decryptCbc(byte[] key, byte[] data) {
		try {
			MachinePacket request = new MachinePacket(MachinePacket.
					TAG_SECRET_DEC_CBC, keyId, 0, key, data);
			return process(request).getData();
		} catch (Exception e) {
			throw new RuntimeException("Connection refused: " + this, e);
		}
	}

	@Override
	public String toString() {
		return keyId + "@" + super.toString();
	}
}